﻿using System;
using System.Collections.Generic;
using HtmlParser.Dom;
using System.IO;
using System.Text;

namespace HtmlParser.Html
{
    public class HtmlDocumentEx : GeneralHtmlParser
    {
        private char[] chars = null;
        private int index = 0;
        private XmlDocument doc = new XmlDocument();
        private Stack<HtmlElement> nodeList = new Stack<HtmlElement>();
        private List<HtmlElement> cacheList = new List<HtmlElement>();
        private char c;
        private int flag;
        private string socHtml = string.Empty;
        private string[] htmls = null;
        public override HtmlElement[] Parse(string[] html)
        {
            if (html == null)
            {
                return new HtmlElement[0];
            }
            htmls = html;
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < html.Length; i++)
            {
                builder.Append(html[i]);
            }
            socHtml = builder.ToString();
            return Parse(socHtml.ToCharArray());
        }

        public override HtmlElement[] Parse(string html)
        {
            if (string.IsNullOrEmpty(html))
            {
                return new HtmlElement[0];
            }

            socHtml = html;
            return Parse(html.ToCharArray());
        }

        public override HtmlElement[] Parse(char[] htmls)
        {
            if (htmls == null)
            {
                return new HtmlElement[0];
            }

            if (string.IsNullOrEmpty(socHtml) && htmls.Length > 0)
            {
                socHtml = new string(htmls);
            }
            chars = htmls;

            // 注释 脚本 内容 声明 DocType 元素 属性
            // 元素包含属性
            //脚本: <script>开始</script>结束
            //元素: <div>  <开始 >或者 /> 结束
            //文本: >开始 <结束
            //注释: <!-- 开始 -->结束
            //节点开始<a>遇到一个相同节点名</a>的反节点，当前节点标记为结束节点，之后的节点加入他的子节点
            ParseDoc();
            // Print(nodeList.ToArray());
            // nodeList.ToArray()
            return nodeList.ToArray();
        }
        public void ParseDoc()
        {
            try
            {
                string name = string.Empty;
                bool isFirst = true;
                while (!EOF)
                {
                    if (!isFirst)
                    {
                        c = GetNextChar();
                    }
                    else
                    {
                        c = Peek();
                        isFirst = false;
                    }
                    if (Peek() == '\0')
                        return;

                    switch (c)
                    {
                        case '<':
                            #region 元素标记

                            if (Peek(1) == '!')
                            {
                                // 如果是指令
                                flag = Laxer.Specify;
                                break;
                            }
                            else
                            {
                                //是普通元素
                                c = GetNextChar();
                                bool isEnd = false;
                                if (c == '/')
                                {
                                    GetNextChar();
                                    isEnd = true;
                                }
                                name = GetNodeName();
                                // 如果当前节点不是反标签
                                if (!isEnd)
                                {
                                    if (string.Compare(name, "script", true) == 0)
                                    {
                                        name = "#script";
                                    }
                                    //处理属性
                                    var temp = AddNode(name, Laxer.ELEMENT_NODE);
                                    ParseAttribute(temp);

                                    if (string.Compare(name, "#script", true) == 0)
                                    {
                                        //script 的结束节点是</script>
                                        var script = ParseScript();
                                        temp.Value = script;
                                        for (int i = 0; i < 8; i++)
                                        {
                                            GetNextChar();
                                        }
                                    }
                                    else
                                    {
                                        if (Peek() == '/' && Peek(1) == '>')
                                        {
                                            GetNextChar();
                                        }
                                    }
                                }
                                else
                                {
                                    //如果是结束节点
                                    AddEndNode(name);
                                }
                                //Move(-1);
                            }
                            #endregion
                            break;
                        case '>':
                            throw new Exception("不应该到达");
                        case '!':
                            if (flag == Laxer.Specify)
                            {
                                #region 处理指令
                                if (Peek(1) == '[')
                                {
                                    #region 处理CData
                                    GetNextChar();
                                    GetNextChar();
                                    name = ParseCData();
                                    if (Peek() == '[')
                                    {
                                        GetNextChar();
                                        string cdataValue = ParseCDataValue();
                                        AddNode("#cdata", cdataValue, Laxer.CDATA_SECTION_NODE);
                                        GetNextChar();
                                        GetNextChar();
                                    }
                                    #endregion
                                    break;
                                }
                                else if (Peek(1) == '-' && Peek(2) == '-')
                                {
                                    #region 处理注释
                                    GetNextChar();
                                    GetNextChar();
                                    GetNextChar();
                                    flag = Laxer.COMMENT_NODE;
                                    string comment = GetComment();

                                    AddNode("#comment", comment, Laxer.COMMENT_NODE);
                                    GetNextChar();
                                    GetNextChar();

                                    #endregion
                                    break;
                                }
                                else
                                {
                                    GetNextChar();
                                    name = GetNodeName();
                                    if (string.Compare(name, "DOCTYPE", true) == 0)
                                    {
                                        string value = GetDocTypeValue();
                                        AddNode("#doctype", value, Laxer.DOCUMENT_TYPE_NODE);
                                    }


                                    break;
                                }
                                #endregion
                            }
                            break;

                        default:
                            #region 处理文本
                            ParseText();
                            #endregion
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                //如果有地方报错，将出错的行输出
                ShowLine();
            }
        }

        private void ShowLine()
        {
            //根据当前的char的index的位置，输出当前行
            int start = this.index;
            int count = 0;
            for (int i = 0; i < htmls.Length; i++)
            {
                string html = htmls[i];
                count += html.Length;
                if (count > start)
                {
                    Console.WriteLine(html);
                    return;
                }
            }
        }

        private void ParseAttribute(HtmlElement parent)
        {

            string attrValue;
            ProcessWhiteSpace();
            while (!EOF && Peek() != '>' && !(Peek() == '/' && Peek(1) == '>'))
            {


                string attrName = ParseAttributeName();

                ProcessWhiteSpace();
                if (Peek() == '=')
                {
                    ProcessWhiteSpace();
                    GetNextChar();
                    c = Peek();
                    if (c == '"' || c == '\'')
                    {
                        GetNextChar();
                        attrValue = ParseAttributeValue(c);
                        GetNextChar();
                    }
                    else
                    {
                        attrValue = ParseAttributeValue2();
                    }
                }
                else
                {
                    attrValue = string.Empty;
                }
                this.AddAttr(parent, attrName, attrValue);
                ProcessWhiteSpace();
            }// end for while
        }

        private void ParseText()
        {
            if (Peek(-1) == '>')
            {
                //ProcessWhiteSpace();
                if (Peek() != '<')
                {
                    // 开始处理文本
                    string text = ParseTextValue();
                    AddNode("#text", text, Laxer.TEXT_NODE);
                    Move(-1);
                }
                //else
                //{
                //    //如果当前是'<'那就回退一个标记
                //    Move(-1);
                //}
            }
        }

        public bool MoveToNextTag()
        {
            if (GetNextChar() == '<')
            {
                return true;
            }
            return !EOF;
        }

        #region 添加节点

        private HtmlAttribute AddAttr(HtmlElement parent, string name, string value)
        {
            var ele = new HtmlAttribute(name, doc);
            ele.Value = value;
            ele.HtmlType = Laxer.ATTRIBUTE_NODE;
            parent.Attributes.AddNode(ele);
            return ele;
        }

        private HtmlElement AddNode(string name, int htmlType)
        {
            var ele = new HtmlElement(name, doc);
            ele.HtmlType = htmlType;
            nodeList.Push(ele);
            return ele;
        }

        private HtmlElement AddNode(string name, string value, int htmlType)
        {
            var ele = new HtmlElement(name, doc);
            ele.Value = value;
            ele.HtmlType = htmlType;
            nodeList.Push(ele);
            return ele;
        }

        private void AddEndNode(string name)
        {
            cacheList.Clear();
            // 为空就结束
            while (nodeList.Count > 0 && !(nodeList.Peek().Name == name && !nodeList.Peek().HasCloseNode))
            {
                var ele = nodeList.Pop();
                cacheList.Add(ele);
            }
            // 如果为空
            if (nodeList.Count > 0)
            {
                var parent = nodeList.Peek();
                parent.CloseNode();
                for (int i = 0; i < cacheList.Count; i++)
                {
                    parent.AppendChild(cacheList[i]);
                }
            }
            else
            {
                for (int i = cacheList.Count - 1; i >= 0; i--)
                {
                    nodeList.Push(cacheList[i]);
                }
            }

        }
        #endregion

        #region 操作

        private string GetDocTypeValue()
        {
            int start = this.index;

            while (!EOF && Peek() != '>')
            {
                Move();
            }
            return socHtml.Substring(start, this.index - start);
        }

        private string GetComment()
        {
            int start = this.index;

            while (!EOF && !(Peek() == '-' && Peek(1) == '-' && Peek(2) == '>'))
            {
                Move();
            }
            return socHtml.Substring(start, this.index - start);
        }

        private string GetNodeName()
        {
            int start = this.index;

            while (!EOF && !Char.IsWhiteSpace(Peek()) && Peek() != '>' && !(Peek() == '/' && Peek(1) == '>'))
            {
                Move();
            }
            return socHtml.Substring(start, this.index - start);
        }

        private string ParseAttributeName()
        {
            int start = this.index;
            while (!EOF && !Char.IsWhiteSpace(Peek()) && Peek() != '>'
              && Peek() != '=')
                Move();
            return socHtml.Substring(start, this.index - start);
        }

        private string ParseAttributeValue(char c)
        {
            int start = this.index;
            while (!EOF && Peek() != c)
                Move();
            return socHtml.Substring(start, this.index - start);
        }

        private string ParseAttributeValue2()
        {
            int start = this.index;
            while (!EOF && !Char.IsWhiteSpace(Peek()))
                Move();
            return socHtml.Substring(start, this.index - start);
        }

        private void ProcessWhiteSpace()
        {
            while (!EOF && Char.IsWhiteSpace(Peek()))
            {
                Move();
            }
        }

        private string ParseCData()
        {
            int start = this.index;

            while (!EOF && Peek() != '[')
            {
                Move();
            }
            return socHtml.Substring(start, this.index - start);
        }

        private string ParseCDataValue()
        {
            int start = this.index;

            while (!EOF && !(Peek() == ']' && Peek(1) == ']' && Peek(2) == '>'))
            {
                Move();
            }
            return socHtml.Substring(start, this.index - start);
        }

        private string ParseTextValue()
        {
            int start = this.index;

            while (!EOF && Peek() != '<')
            {
                Move();
            }
            return socHtml.Substring(start, this.index - start);
        }

        private string ParseScript()
        {
            //去掉script结束标记'>'以及'>'以前的空格
            ProcessWhiteSpace();
            //如果 Peek 返回值是 >
            if (Peek() == '>')
            {
                GetNextChar();
            }

            int start = this.index;

            while (!EOF && !(Peek() == '<' && Peek(1) == '/'
                && (Peek(2) == 's' || Peek(2) == 'S')
                && (Peek(3) == 'c' || Peek(3) == 'C')
                && (Peek(4) == 'r' || Peek(4) == 'R')
                && (Peek(5) == 'i' || Peek(5) == 'I')
                && (Peek(6) == 'p' || Peek(6) == 'P')
                && (Peek(7) == 't' || Peek(7) == 'T')
                && Peek(8) == '>'))
            {
                Move();
            }
            return socHtml.Substring(start, this.index - start);
        }

        private bool EOF
        {
            get { return (index >= chars.Length); }
        }

        private char Peek()
        {
            return Peek(0);
        }

        private char Peek(int ahead)
        {
            int pos = (index + ahead);
            if (pos < chars.Length && pos >= 0)
                return chars[pos];
            return (char)0;
        }

        private void Move()
        {
            Move(1);
        }

        private void Move(int ahead)
        {
            index = Math.Min(index + ahead, chars.Length);
        }

        private char GetNextChar()
        {
            Move();
            return Peek(0);
        }
        #endregion

        private XmlDocument CreateDoc()
        {
            if (nodeList != null && nodeList.Count > 0)
            {
                HtmlElement[] nodes = nodeList.ToArray();
                for (int i = nodes.Length - 1; i >= 0; i--)
                {
                    var item = nodes[i];
                    doc.AppendChild(item);
                }
            }
            return doc;
        }

        private void Print(HtmlElement[] nodes)
        {
            for (int i = nodes.Length - 1; i >= 0; i--)
            {
                var item = nodes[i];
                Write(item.Name + ":" + item.Value);
                if (item.ChildNodes != null && item.ChildNodes.Count > 0)
                {
                    PrintChild(item, 0);
                }
            }
            WriteFile();
        }// end for method

        private void PrintChild(HtmlElement parent, int index)
        {
            for (int i = parent.ChildNodes.Count - 1; i >= 0; i--)
            {
                var item = parent.ChildNodes[i];
                //String format = "\t";
                //for (int j = 0; j < index; j++)
                //{
                //    format += format;
                //}
                Write(index + ":" + item.Name + ":" + item.Value);
                if (item.ChildNodes != null && item.ChildNodes.Count > 0)
                {
                    PrintChild(item as HtmlElement, index + 1);
                }
            }
        }
        System.Text.StringBuilder builder = new System.Text.StringBuilder();
        private void Write(String str)
        {
            //Console.WriteLine(str);
            builder.Append(str);
            builder.Append("\n");
        }

        private void WriteFile()
        {
            StreamWriter writer = new StreamWriter("d:/test.txt", false);
            writer.WriteLine(builder.ToString());
            writer.Close();
        }
    }
}
